if ( !window.wala )
  window.wala = { };
if ( !window.wala.Simulation )
  window.wala.Simulation = { };

/*******************************************************
 *
 * Global wala functions specific to Wave.
 * This implementation offers a simulation only because it does not
 * talk to the wave server. All operations are carried out locally
 *
 *******************************************************/

wala.Simulation.wave = function( id, domain )
{
  return wala.Simulation.Wave.getWave( id, domain );
};

wala.Simulation.wavelets = function( wave )
{
  wave.getEvent("newWavelet").addListenerOnce( this.setDirty, this );
  var result = [ ];
  var wavelets = wave.getWavelets();
  for( var key in wavelets )
	result.push( wavelets[key] ); 
  return result;
};

wala.Simulation.doc = function( wavelet, id )
{
  var doc = wavelet.getDoc(id);
  return doc;
};

wala.Simulation.text = function(d)
{
  if ( !d )
	return "";
  d.getEvent("change").addListenerOnce( this.setDirty, this );
  return d.getText();
};

/*******************************************************
 *
 * Simulated Wave Document
 *
 *******************************************************/

wala.Simulation.Document = function()
{
};

wala.Simulation.Document.prototype.createElement = function(tag, attribs)
{
  return new wala.Simulation.Element_(tag, attribs, doc );
};

wala.Simulation.Document.prototype.createTextNode = function(txt)
{
  return new wala.Simulation.TextNode_(tag, attribs, doc );
};

wala.Simulation.Document.prototype.emitChangeEvent = function()
{
  if ( this.change_event )
   this.change_event.emit();
};

wala.Simulation.Document.prototype.getEvent = function(name)
{
  if ( name == "change" )
  {
    if ( !this.change_event )
	  this.change_event = new wala.Event();
	return this.change_event;
  }
  return null;
};
	 
wala.Simulation.Document.prototype.getText = function()
{
  var result = [ ];
  
  var p = this.firstChild;
  while( p )
  {
	result.push( p.getText() );
	p = p.nextSibling;
  }
  
  return result.join();
};

wala.Simulation.Document.prototype.rootNode = function()
{
  return this;
};

wala.Simulation.Document.prototype.insertBefore = function(newNode, beforeNode)
{
  assert( node.doc == this.rootNode(), "Node has not been created in this document" );
  assert( !beforeNode || beforeNode.parentNode == this, "Node has not been created in this document" );
  
  if ( newNode.parentNode )
	newNode.parentNode.removeChild(newNode);
  
  newNode.parentNode = this;
  if ( !this.firstChild )
  {
	this.firstChild = newNode;
	this.lastChild = newNode;
	newNode.previousSibling = null;
	newNode.nextSibling = null;
  }
  else
  {
	if ( this.firstChild == beforeNode )
	  this.firstChild = newNode;
	if ( beforeNode )
	{
	  if ( beforeNode.previousSibling )
		beforeNode.previousSibling.nextSibling = newNode;
	  newNode.previousSibling = beforeNode.previousSibling;
	  newNode.nextSibling = beforeNode;
	  beforeNode.previousSibling = newNode;
	}
	else
	{
	  this.lastChild.nextSibling = newNode;
	  newNode.previousSibling = this.lastChild;
	  newNode.nextSibling = null;
	  this.lastChild = newNode;
	}
  }

  this.emitChangeEvent();
};

wala.Simulation.Document.prototype.appendChild = wala.Simulation.Document.prototype.insertBefore;

wala.Simulation.Document.prototype.removeChild = function(node)
{
  assert(node.parentNode == this);
  
  delete node.parentNode;
  if ( this.firstChild == node )
	this.firstChild = node.nextSibling;
  if ( this.lastChild == node )
	this.lastChild = node.previousSibling;
  if ( node.nextSibling )
	node.nextSibling.previousSibling = node.previousSibling;
  if ( node.previousSibling )
	node.previousSibling.nextSibling = node.nextSibling;
  delete node.previousSibling;
  delete node.nextSibling;
  
  this.emitChangeEvent();
};

wala.Simulation.Document.prototype.replaceChild = function(oldNode, newNode)
{
  assert(oldNode.parentNode == this);

  if ( newNode.parentNode )
	newNode.parentNode.removeChild(newNode);
	
  newNode.parentNode = this;
  newNode.previousSibling = oldNode.previousSibling;
  newNode.nextSibling = oldNode.nextSibling;
  if ( newNode.previousSibling )
	newNode.previousSibling.nextSibling = newNode;
  if ( newNode.nextSibling )
	newNode.nextSibling.previousSibling = newNode;
  
  oldNode.parentNode = null;
  oldNode.previousSibling = null;
  oldNode.nextSibling = null;
  
  this.emitChangeEvent();
};

wala.Simulation.Element_ = function(txt, doc)
{
  this.tag = tag;
  this.attributes = attribs;
  this.doc = doc;
};

wala.Simulation.Element_.prototype.emitChangeEvent = function()
{
  if ( this.parentNode )
    this.parentNode.emitChangeEvent();
};

wala.Simulation.Element_.prototype.setAttribute = function( key, value )
{
  if ( !this.attributes )
	this.attributes = [ ];
  if ( this.attributes[key] != value )
  {
    this.attributes[key] = value;
	this.emitChangeEvent();
  }
};

wala.Simulation.Element_.prototype.getAttribute = function( key )
{
  if ( !this.attributes )
	return null;
  return this.attributes[key];
};

wala.Simulation.Element_.prototype.rootNode = function()
{
  return this.doc;
};

wala.Simulation.Element_.prototype.getText = wala.Simulation.Document.prototype.getText;
wala.Simulation.Element_.prototype.insertBefore = wala.Simulation.Document.prototype.insertBefore;
wala.Simulation.Element_.prototype.appendChild = wala.Simulation.Document.prototype.appendChild;
wala.Simulation.Element_.prototype.replaceChild = wala.Simulation.Document.prototype.replaceChild;
wala.Simulation.Element_.prototype.removeChild = wala.Simulation.Document.prototype.removeChild;

wala.Simulation.TextNode_ = function(txt, doc)
{
  this.text = txt;
  this.doc = doc;
};

wala.Simulation.TextNode_.prototype.rootNode = function()
{
  return this.doc;
};

wala.Simulation.TextNode_.prototype.setText = function(txt)
{
  if ( txt != this.text )
  {
    this.text = txt;
	this.emitChangeEvent();
  }
};

wala.Simulation.TextNode_.prototype.getText = function()
{
  return this.text;
};

wala.Simulation.TextNode_.prototype.emitChangeEvent = function()
{
  if ( this.parentNode )
    this.parentNode.emitChangeEvent();
};

/////////////////////////////////////////////////
//
// Simulated Waves
//
/////////////////////////////////////////////////

/**
 * Do not create waves directly. Use getWave instead.
 * @constructor
 * @see JSOT#getWave
 */
wala.Simulation.Wave = function(id, domain)
{
  wala.Simulation.Wave.waves[ id + "$" + domain] = this;
  this.id = id;
  this.domain = domain;
  this.wavelets = { };
};

/**
 * Dictionary of all wave objects. This is NOT equal to all waves a user might have access to.
 * @private
 */
wala.Simulation.Wave.waves = { };

/**
 * Gets or creates a wave.
 *
 * @param id is the wave-id, such as "w+1234".
 * @param domain is the domain of the wave such as "foobar.com".
 */
wala.Simulation.Wave.getWave = function(id, domain)
{
  var wname = id + "$" + domain;
  var w = wala.Simulation.Wave.waves[wname];
  if ( w )
    return w;
  w = new wala.Simulation.Wave( id, domain );
  wala.Simulation.Wave.waves[wname] = w;
  
  return w;
};

/**
 * Gets or creates a wavelet of this wave.
 *
 * @param id is the wavelet-id, such as "conv+root".
 * @param domain is the domain of the wavelet such as "foobar.com". 
 */
wala.Simulation.Wave.prototype.getWavelet = function(id, domain)
{
  var wname = id + "$" + domain;
  var w = this.wavelets[wname];
  if ( w )
    return w;
  w = new wala.Simulation.Wavelet(this, id, domain);
  this.wavelets[wname] = w;

  if ( this.new_wavelet_event )
	this.new_wavelet_event.emit( w );

  return w;
};

wala.Simulation.Wave.prototype.getWavelets = function()
{
  return this.wavelets;
};

wala.Simulation.Wave.prototype.getEvent = function( event_type )
{
  if ( event_type == "newWavelet" )
  {
	if ( !this.new_wavelet_event )
	  this.new_wavelet_event = new wala.Simulation.Event(this);
	return this.new_wavelet_event;
  }
  return null;
};

/////////////////////////////////////////////////
//
// Simulated Wavelets
//
/////////////////////////////////////////////////

/**
 * Do not use this function directly. Use Wave.getWavelet instead.
 * @constructor
 *
 * @param wave is the wave containing the wavelet.
 * @param id is the wavelet-id, such as "conv+root".
 * @param domain is the domain of the wavelet such as "foobar.com".   
 */
wala.Simulation.Wavelet = function(wave, id, domain)
{
  /**
   * The id of the wave, for example "conv+root"
   * @type string
   */
  this.id = id;
  /**
   * The domain of the wave, for example "example.com"
   * @type string
   */
  this.domain = domain;
  /**
   * The wave to which this wavelet belongs
   * @type wala.Simulation.Wave
   */  
  this.wave = wave;
  this.documents = { };
  /**
   * The participants of the wave.
   * @type string[]
   */
  this.participants = [ ];

  wala.Simulation.Wavelet.wavelets[ this.url().toString() ] = this;
};

/**
 * A dictionary of all wavelet objects indexed by their URL.
 */
wala.Simulation.Wavelet.wavelets = { };

/**
 * Gets a wavelet or returns false if it does not exist.
 *
 * @param wavelet_name is a URL of the form wave://waveletDomain/waveDomain$waveId/waveletId.
 */
wala.Simulation.Wavelet.getWavelet = function( wavelet_name )
{
  return wala.Simulation.Wavelet.wavelets[ wavelet_name ];
};

/**
 * Gets or creates a wavelet document.
 *
 * @param docname is a document name such as "b+124".
 * @param return an instance of wala.Simulation.Doc.
 */
wala.Simulation.Wavelet.prototype.getDoc = function(docname)
{
  var doc = this.documents[docname];
  if ( doc )
    return doc;
  doc = new wala.Simulation.Doc(docname, this);
  this.documents[docname] = doc;
  return doc;
};

wala.Simulation.Wavelet.prototype.getDocs = function()
{
  return this.documents;
};

/**
 * @return the URL of this wavelet.
 */
wala.Simulation.Wavelet.prototype.url = function()
{
  var url = new wala.Simulation.WaveUrl();
  url.waveletDomain = this.domain;
  url.waveletId = this.id;
  url.waveDomain = this.wave.domain;
  url.waveId = this.wave.id;
  return url;
};

/**
 * @return the content and participant of a wavelet as a multi-line string.
 */
wala.Simulation.Wavelet.prototype.toString = function()
{
  var str = this.url().toString() + "\r\n\tParticipants:\r\n";  
  
  for( var a in this.participants )
  {
    str += "\t\t" + this.participants[a] + "\r\n";
  }
  
  str += "\tDocuments:\r\n";
  for( var a in this.documents )
  {
    str += "\t\t" + a + ":" + this.documents[a].toString() + "\r\n";
  }
  
  return str;
};

/**
 * Submits a request for adding a participant. The local wavelet is changed immediately
 * and the request is being sent to the server.
 */
wala.Simulation.Wavelet.prototype.addParticipant = function(jid)
{
  var i = this.participants.indexOf(jid);
  if ( i != -1 )
    return;
  this.participants.push( jid );
};

/**
 * Submits a request for removing a participant. The local wavelet is changed immediately
 * and the request is being sent to the server.
 */
wala.Simulation.Wavelet.prototype.removeParticipant = function(jid)
{
  var i = this.participants.indexOf(jid);
  if ( i == -1 )
    return;
  delete this.participants[i];
};
